#include "limbo/details/checksum.h"
#include <cstddef>

namespace limbo::details {

uint16_t checksum(const void *data, uint32_t len) {
  // taken from lwip/src/core/inet_chksum.c

  using u8_t = uint8_t;
  using u16_t = uint16_t;
  using u32_t = uint32_t;
  using mem_ptr_t = ptrdiff_t;

#define FOLD_U32T(u) ((u32_t)(((u) >> 16) + ((u)&0x0000ffffUL)))
#define SWAP_BYTES_IN_WORD(w) (((w)&0xff) << 8) | (((w)&0xff00) >> 8)

  const u8_t *pb = (const u8_t *)data;
  const u16_t *ps;
  u16_t t = 0;
  const u32_t *pl;
  u32_t sum = 0, tmp;
  /* starts at odd byte address? */
  int odd = ((mem_ptr_t)pb & 1);

  if (odd && len > 0) {
    ((u8_t *)&t)[1] = *pb++;
    len--;
  }

  ps = (const u16_t *)(const void *)pb;

  if (((mem_ptr_t)ps & 3) && len > 1) {
    sum += *ps++;
    len -= 2;
  }

  pl = (const u32_t *)(const void *)ps;

  while (len > 7) {
    tmp = sum + *pl++; /* ping */
    if (tmp < sum) {
      tmp++; /* add back carry */
    }

    sum = tmp + *pl++; /* pong */
    if (sum < tmp) {
      sum++; /* add back carry */
    }

    len -= 8;
  }

  /* make room in upper bits */
  sum = FOLD_U32T(sum);

  ps = (const u16_t *)pl;

  /* 16-bit aligned word remaining? */
  while (len > 1) {
    sum += *ps++;
    len -= 2;
  }

  /* dangling tail byte remaining? */
  if (len > 0) { /* include odd byte */
    ((u8_t *)&t)[0] = *(const u8_t *)ps;
  }

  sum += t; /* add end bytes */

  /* Fold 32-bit sum to 16 bits
     calling this twice is probably faster than if statements... */
  sum = FOLD_U32T(sum);
  sum = FOLD_U32T(sum);

  if (odd) {
    sum = SWAP_BYTES_IN_WORD(sum);
  }

  sum = SWAP_BYTES_IN_WORD(sum);

  return (u16_t)sum;
}

uint16_t checksum_add(uint16_t r, uint16_t l) {
  auto acc = (uint32_t)r + (uint32_t)l;
  acc = (acc >> 16) + (acc & 0x0000ffffUL);
  if ((acc & 0xffff0000UL) != 0) {
    acc = (acc >> 16) + (acc & 0x0000ffffUL);
  }
  return (uint16_t)acc;
}

uint16_t checksum_sub(uint16_t r, uint16_t l) {
  auto res = r - l;
  if (r < l) {
    --res;
  }
  return (uint16_t)res;
}

} // namespace limbo::details
